/*  PrimeBlaster
    by Kjetil Eide 
    kjetile445@gmail.com

    This file is part of PrimeBlaster

    PrimeBlaster is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    PrimeBlaster is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with PrimeBlaster.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.eide.spaceship;

import org.newdawn.slick.Sound;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.geom.Polygon;
import org.newdawn.slick.SlickException;

public class Collision {
	public static final float ERROR_MARGIN = 10;
	private int uniqueCollisions = 0;
	private CollisionResponse cr;
	private Sound metalBoom;
	//private Sound rockBang;

	public Collision() throws SlickException {
		cr = null;
		metalBoom = new Sound("spacedata/QUIETUSboom.ogg");
		//rockBang = new Sound("spacedata/DYNAMITE.ogg");
	}

	/* simple check to see if there are any collisions, 
	 * but don't do anything about it.
	 * used when posistioning objects randomly on screen
	 */
	public boolean haveCollisions(RBCollection rBody) {
		boolean haveCollision = false; 
		
		for (int i = 0; i < rBody.getNumberOfRB(); i++) {
			for (int j = 0; j < rBody.getNumberOfRB(); j++) {
				if (j != i) {
					if (rBody.getRB(i).getShape().intersects(rBody.getRB(j).getShape())) {
						haveCollision = true;
					}
				}
			}
			
		}
		return haveCollision;
	}
	
	/* Check for collisions and handle the collision response.
	 * Sjekk om en kollisjon allerede er h�ndtert ved � lagre kollisjonspunktet
	 * i cr objektet. kollisjonen er h�ndtert hvis et cr objekt med samme punkt
	 * (+- slingringsmonn) og samme body-objekter eksisterer
	 */

	public void handleAllCollisions(RBCollection rBody) {
		CollisionResponse newCR = null;
		boolean sameCollision = false;
		uniqueCollisions = 0;
		
		for (int i = 0; i < rBody.getNumberOfRB(); i++) {
			for (int j = i + 1; j < rBody.getNumberOfRB(); j++) {
				newCR = checkCollision(rBody.getRB(i), rBody.getRB(j));
				if (newCR != null) {
					if (cr != null) {
						float oldX = cr.getColP().x;
						float oldY = cr.getColP().y;
						float newX = newCR.getColP().x;
						float newY = newCR.getColP().y;
						// System.out.println("old: ("+oldX+","+oldY+") new ("+newX+","+newY+")");
						if (newX > oldX - ERROR_MARGIN
								&& newX < oldX + ERROR_MARGIN
								&& newY > oldY - ERROR_MARGIN
								&& newY < oldY + ERROR_MARGIN) {
							//System.out.print("samme kollisjon");
							sameCollision = true;
						}
					}
					if (!sameCollision) {
						uniqueCollisions++;
						//System.out.println(uniqueCollisions + ". kollisjon");
						cr = newCR;
						cr.doInstantaneousCollision();
						playCollisionSound(rBody.getRB(i), rBody.getRB(j));
					}
				}
			}
		}
//		if (uniqueCollisions > 1)
//			System.out.println("fant "+uniqueCollisions+" kollisjoner");
	}

	private CollisionResponse checkCollision(RigidBody bodyA, RigidBody bodyB) {
		{
			Polygon polygonA = bodyA.getShape();
			Polygon polygonB = bodyB.getShape();

			int polygonAPoints = polygonA.getPointCount();
			int polygonBPoints = polygonB.getPointCount();
			
			// potential separation axes. they get converted into push vectors
			Vector2f[] axis = new Vector2f[32]; // max of 16 vertices per
												// polygon

			int iNumAxis = 0;
			int j = polygonAPoints - 1;
			

			for (int i = 0; i < polygonA.getPointCount(); i++) {
				Vector2f vertexI = new Vector2f(polygonA.getPoint(i)[0],
						polygonA.getPoint(i)[1]);
				Vector2f vertexJ = new Vector2f(polygonA.getPoint(j)[0],
						polygonA.getPoint(j)[1]);
				Vector2f edge = vertexI.sub(vertexJ);
				Vector2f edgePerp = new Vector2f(-edge.y, edge.x);
				axis[iNumAxis] = axisSeparatePolygons(edgePerp, polygonA,
						polygonB);

				if (axis[iNumAxis] == null) {
					return null; // not colliding
				}

				iNumAxis++;
				
				j = (j + 1) % polygonAPoints;
				
			}

			j = polygonBPoints - 1;

			for (int i = 0; i < polygonB.getPointCount(); i++) {
				Vector2f vertexI = new Vector2f(polygonB.getPoint(i)[0],
						polygonB.getPoint(i)[1]);
				Vector2f vertexJ = new Vector2f(polygonB.getPoint(j)[0],
						polygonB.getPoint(j)[1]);
				Vector2f edge = vertexI.sub(vertexJ);
				Vector2f edgePerp = new Vector2f(-edge.y, edge.x);
				axis[iNumAxis] = axisSeparatePolygons(edgePerp, polygonA,
						polygonB);

				if (axis[iNumAxis] == null) {
					return null; // not colliding
				}

				iNumAxis++;
				
				j = (j + 1) % polygonBPoints;
			}

			// find the MTD among all the separation vectors
			Vector2f mtd = FindMTD(axis, iNumAxis);

			// makes sure the push vector is pushing A away from B

			Vector2f d = new Vector2f(bodyA.getPos()).sub(bodyB.getPos());
			if (d.dot(mtd) < 0.0f)
				mtd = mtd.negate();

			CollisionResponse cr = new CollisionResponse(bodyA, bodyB, mtd);
			
			if (cr.getColP() == null) {
				System.out.println("no collision point!");
				return null;
			} else {
				
				return new CollisionResponse(bodyA, bodyB, mtd);
			}
		}
	}

	/**
	 * Check if two polygons are colliding with Separating Axis Theorem
	 * 
	 * @param axis
	 * @param polygonA
	 * @param polygonB
	 * @return null if not colliding the minimum translation distance vector if
	 *         they are colliding. this is the minimum vector required to push
	 *         the objects apart to stop them from intersecting.
	 */

	private Vector2f axisSeparatePolygons(Vector2f axis, Polygon polygonA,
			Polygon polygonB) {
		float[] interval;
		float mina, maxa;
		float minb, maxb;

		interval = CalculateInterval(axis, polygonA);
		mina = interval[0];
		maxa = interval[1];

		interval = CalculateInterval(axis, polygonB);
		minb = interval[0];
		maxb = interval[1];

		if (mina > maxb || minb > maxa)
			return null; // polygonene er adskilte

		// find the interval overlap
		float d0 = maxa - minb;
		float d1 = maxb - mina;
		float depth = (d0 < d1) ? d0 : d1;

		// convert the separation axis into a push vector (re-normalise
		// the axis and multiply by interval overlap)
		float axis_length_squared = axis.dot(axis);

		axis.scale(depth / axis_length_squared);
		return axis;
	}

	private Vector2f FindMTD(Vector2f[] PushVector, int iNumVectors) {
		Vector2f mtd = PushVector[0];
		float mind2 = PushVector[0].dot(PushVector[0]);

		for (int i = 1; i < iNumVectors; i++) {
			float d2 = PushVector[i].dot(PushVector[i]);
			if (d2 < mind2) {
				mind2 = d2;
				mtd = PushVector[i];
			}
		}
		return mtd;
	}

	private float[] CalculateInterval(Vector2f axis, Polygon p) {
		float d = axis.dot(new Vector2f(p.getPoint(0)[0], p.getPoint(0)[1]));
		float min, max;
		min = max = d;

		for (int i = 0; i < p.getPointCount(); i++) {
			d = new Vector2f(p.getPoint(i)[0], p.getPoint(i)[1]).dot(axis);
			if (d < min)
				min = d;
			else if (d > max)
				max = d;
		}

		float[] minMax = new float[2];
		minMax[0] = min;
		minMax[1] = max;

		return minMax;
	}
	
	private void playCollisionSound(RigidBody rb1, RigidBody rb2) {
		if (rb1 instanceof SpaceShip || rb2 instanceof SpaceShip) {
			metalBoom.play();
		} 
	}
}
